#include "AdministradorPartidas.h"
AdministradorPartidas::AdministradorPartidas(bool atachado) :
		Thread(atachado) {
	this->torneo = NULL;
}

AdministradorPartidas::~AdministradorPartidas() {
}

void AdministradorPartidas::run() {
	signal(SIGURG, AdministradorPartidas::signalHandler);
	this->crearPartida();
	pthread_exit(0);
}

void AdministradorPartidas::inicializarPartidaPuntajeRanking(t_partida* partida) {
	/* Y genero el ranking para cada jugador en esta partida*/
	this->torneo->getMutexRanking()->P();
	std::list<t_estadistica_jugador>::iterator it = this->torneo->getRanking()->begin();
	for (; it != this->torneo->getRanking()->end(); ++it) {
		if (it->jugador.socket_id == partida->jugador1.socket_id || it->jugador.socket_id == partida->jugador2.socket_id) {
			it->partidas_puntajes[partida->partidaId] = 0;
		}
	}
	this->torneo->getMutexRanking()->V();
}

void AdministradorPartidas::signalHandler(int signal) {
	switch (signal) {
	case SIGURG:
		pthread_exit(NULL);
	}
}

void AdministradorPartidas::crearPartida() {
	while (!this->torneo->isFinalizado()) {
		//Intento generar una partida
		t_partida* partida = this->intentarPartida();
		//genero un servidor de partida
		if (partida != NULL && this->crearServidorPartida(partida)) {
			/* Si se generó correctamente encolo la partida. */
			this->enlistarPartida(partida);
			/* Y genero el ranking para cada jugador en esta partida*/
			this->inicializarPartidaPuntajeRanking(partida);
			/* Si no arranco el torneo todavia, lo arranca */
			if (!this->torneo->isIniciado()) {
				this->torneo->getTorneoLogger()->info("Comenzando el torneo");
				this->torneo->calcularTiempoTorneo();
				this->torneo->getReloj()->start();
				this->torneo->getAdministradorEstadisticas()->start();
				this->torneo->getIngresoTeclado()->start();
				this->torneo->setIniciado(true);
			}
		}
		usleep(100000);
	}
}

t_partida *AdministradorPartidas::inicializarPartida(const t_jugador *jugadorUno, const t_jugador *jugadorDos) {
	/* Tomo los datos de los jugadores y los incluyo en la partida */
	t_partida *partida = new t_partida;
	partida->activo = true;
	partida->jugador1 = *jugadorUno;
	partida->jugador2 = *jugadorDos;
	/* Inicializo en cero sus puntajes */
	partida->jugador2.puntaje = 0;
	partida->jugador1.puntaje = 0;
	return partida;
}
/**
 * Genero una partida nueva y verifico que no exista.
 * En caso de que exista retorno NULL
 * de lo contrario retorno un puntero a a dicha partida creada.
 */
t_partida* AdministradorPartidas::intentarPartida() {
	//Recupero 2 jugadores disponibles en forma sincronizadas
	t_partida* prtPartida = NULL;
	this->torneo->getMutexJugadoresDisponibles()->P();
	std::list<t_jugador>::iterator itUno = this->torneo->getJugadoresDisponibles().begin();
	std::list<t_jugador>::iterator itDos = this->torneo->getJugadoresDisponibles().begin();
	bool partidaGenerada = false;
	for (;!partidaGenerada && itUno != this->torneo->getJugadoresDisponibles().end(); ++itUno) {
		itDos = itUno;
		itDos++;
		for (;!partidaGenerada && itDos != this->torneo->getJugadoresDisponibles().end(); ++itDos) {
			//Inicializo la partida
			t_partida *partida = this->inicializarPartida(&(*itUno), &(*itDos));
			if (this->noExistePartida(partida)) {
				this->torneo->getMutexJugadoresDisponibles()->V();
				this->torneo->desenlistarJugadorDisponible(itUno);
				this->torneo->desenlistarJugadorDisponible(itDos);
				prtPartida = partida;
				partidaGenerada = true;
			}else{
				free(partida);
			}
		}
	}
	if(!partidaGenerada){
		this->torneo->getMutexJugadoresDisponibles()->V();
	}
	return prtPartida;
}

bool AdministradorPartidas::noExistePartida(t_partida *partida) {
	Torneo* torneo = this->torneo;
	torneo->getMutexPartidas()->P();
	std::list<t_partida>::iterator it = torneo->getPartidas().begin();
	for (; it != this->torneo->getPartidas().end(); ++it) {
		if (this->compararPartidas(partida, &(*it))) {
			torneo->getMutexPartidas()->V();
			return false;
		}
	}
	torneo->getMutexPartidas()->V();
	return true;
}

void AdministradorPartidas::actualizarPartidas() {
	t_partida aux;
	this->torneo->getMutexPartidas()->P();
	std::list<t_partida>::iterator it = this->torneo->getPartidas().begin();
	for (; it != this->torneo->getPartidas().end(); ++it) {
		aux = this->torneo->getAdministradorMemoriaCompartida()->leerDatos(it->partidaId);
		it->jugador1.puntaje = aux.jugador1.puntaje;
		it->jugador2.puntaje = aux.jugador2.puntaje;
		it->jugador1.conectado = aux.jugador1.conectado;
		it->jugador2.conectado = aux.jugador2.conectado;
	}
	this->torneo->getMutexPartidas()->V();
}

bool AdministradorPartidas::compararPartidas(t_partida *partidaA, t_partida *partidaB) {
	bool sonIguales = false;
	if (partidaA->jugador1.socket_id == partidaB->jugador1.socket_id) {
		if (partidaA->jugador2.socket_id == partidaB->jugador2.socket_id) {
			sonIguales = true;
		}
	}
	if (partidaA->jugador1.socket_id == partidaB->jugador2.socket_id) {
		if (partidaA->jugador2.socket_id == partidaB->jugador1.socket_id) {
			sonIguales = true;
		}
	}
	return sonIguales;
}

void AdministradorPartidas::enlistarPartida(t_partida *partida) {
	Torneo* torneo = this->torneo;
	torneo->getMutexPartidas()->P();
	torneo->getPartidas().push_back(*partida);
	torneo->addPartidas();
	torneo->getMutexPartidas()->V();
}

bool AdministradorPartidas::crearServidorPartida(t_partida *partida) {
	this->torneo->getTorneoLogger()->info("Creando servidor de partida");
	int pidServidorPartida = fork();
	if (pidServidorPartida == 0) {
		partida->partidaId = getpid();
		this->crearMemoriaCompartida(partida, false);
		execv("TP4_SERVIDOR_PARTIDA", NULL);
		exit(0);
	} else if (pidServidorPartida > 0) {
		sleep(1);
		partida->partidaId = pidServidorPartida;
		this->crearMemoriaCompartida(partida, true);
	} else {
		this->torneo->getTorneoLogger()->info("Error creando el servidor de partida");
		return false;
	}
	return true;
}

void AdministradorPartidas::terminarPartida() {
}

void AdministradorPartidas::crearMemoriaCompartida(t_partida *partida, bool liberar) {
	this->torneo->getAdministradorMemoriaCompartida()->asignarShm(partida->partidaId, liberar);
	this->torneo->getAdministradorMemoriaCompartida()->escribirDatos(partida, partida->partidaId);
}

// Getters & setters

Torneo* AdministradorPartidas::getTorneo() {
	return torneo;
}

void AdministradorPartidas::setTorneo(Torneo* torneo) {
	this->torneo = torneo;
}

